/*
 * @Author: xiaosihan
 * @Date: 2021-05-22 00:31:54
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2021-11-28 16:51:03
 */

import Object3D from "../Object3D/Object3D";
import React from "react";
import * as THREE from "three";
import Group3D from "../Group3D/Group3D";
import Line2 from "../Line2/Line2";
import Object3DComponent, { Object3DComponentProps } from "../Object3D/Object3DComponent";
import { Vector3 } from "../Object3D/type";
import Raycaster from "../Raycaster/Raycaster";
import xrt_util from "../xrt_util/xrt_util";


export interface FPSControlsProps extends Object3DComponentProps {
    initTime?: number, // 初始时间 当初始时间改变时,位置和中心点会被重置
    position?: Vector3,
    enabled?: boolean, // 启用
    collide?: boolean, // 碰撞检测
    collideVolume?: number, // 碰撞体积
    mouseSpeed?: number,// 鼠标速度
    moveSpeed?: number, // 水平移动速度
    lock?: boolean,// 是否锁定鼠标
    lockCall?: () => void, // 锁定的回调
    unlockCall?: () => void, // 解锁的回调
    forward?: string[], // 前进按钮
    backward?: string[], // 后台按钮
    left?: string[], // 左移按钮
    right?: string[], // 右移按钮
    jump?: string[], // 跳跃的力量
    keyDown?: (e: KeyboardEvent) => void, // 键盘按下事件
    keyUp?: (e: KeyboardEvent) => void, // 键盘弹起事件
    gravity?: boolean,//重力
    jumpPower?: number, // 跳跃的力量
    maxAzimuthAngle?: number, // 最大方位角 与负Y轴的夹角
    minAzimuthAngle?: number // 最小方位角 与正Y轴的夹角
}

interface State { }

export default class FPSControls extends Object3DComponent<FPSControlsProps, State> {
    constructor(props: FPSControlsProps) {
        super(props);
    }

    static defaultProps = {
        initTime: 0, // 初始时间 当初始时间改变时,位置和中心点会被重置
        position: { x: 0, y: 0, z: 0 },
        enabled: true, // 启用
        collide: true, // 碰撞检测
        collideVolume: 3, // 碰撞体积
        mouseSpeed: 0.5,// 鼠标速度
        moveSpeed: 10, // 水平移动速度
        lock: false,// 是否锁定鼠标
        lockCall: () => { }, // 锁定的回调
        unlockCall: () => { }, // 解锁的回调

        forward: ["w"], // 前进按钮
        backward: ["s"], // 后台按钮
        left: ["a"], // 左移按钮
        right: ["d"], // 右移按钮
        jump: [" "],// 跳跃按键

        keyDown: (e: KeyboardEvent) => { }, // 键盘按下事件
        keyUp: (e: KeyboardEvent) => { }, // 键盘弹起事件
        gravity: false,//重力
        jumpPower: 1.5, // 跳跃的力量
        maxAzimuthAngle: 79, // 最大方位角 与负Y轴的夹角
        minAzimuthAngle: -79 // 最小方位角 与正Y轴的夹角
    }

    // 时钟对象
    clock = new THREE.Clock();

    // 射线拾取器是位置增量
    positionIncremental = new THREE.Vector3();

    collideDirection = new THREE.Vector3(0, 0, 0); // 碰撞方向的合成 由4个射线拾取合成

    verticalCollisionDirection = new THREE.Vector3(0, 0, 0); // 垂直碰撞的方向

    // 控制器的状态
    _state = {
        initTime: -1, // 初始时间
        forwarding: false, // 前进
        backwarding: false, // 后退
        lefting: false, // 左移
        righting: false, // 右移
        move: false, // 是否激活鼠标转向
        addSpeed: new THREE.Vector3(0, -0.1, 0), // 下降加速度
        speed: new THREE.Vector3(0, 0, 0), // 下降速度
        locked: false, // 是否锁住了鼠标
    }

    upTodwomRay!: Raycaster // 上向下 射线
    dwomToUpRay!: Raycaster// 下向上 射线

    rightToForwardRay!: Raycaster  // 右向前 射线
    leftToForwardRay!: Raycaster   // 前向左 射线 
    leftToBackRay!: Raycaster  // 左向后 射线
    rightToBackRay!: Raycaster // 右向后 射线

    angle = Math.PI / 180 * 44.9; // 下落时倾斜角度大于45度时 沿着倾斜面往下滑动

    // 用于相对计算的 欧拉角
    euler = new THREE.Euler(0, 0, 0, "YXZ");

    static vector3Temp = new THREE.Vector3();

    keydown = (e: KeyboardEvent) => {



        const {
            forward = ["w"],
            backward = ["s"],
            left = ["a"],
            right = ["d"],
            jump = [" "],
            jumpPower = 0,
            gravity = true,
            enabled = true,
            keyDown
        } = this.props;


        if (!enabled) {
            return;
        }

        forward.includes(e.key) && (this._state.forwarding = true); // 开启前进状态
        backward.includes(e.key) && (this._state.backwarding = true); // 开启后退状态
        left.includes(e.key) && (this._state.lefting = true); // 开启左移状态
        right.includes(e.key) && (this._state.righting = true); // 开启右移状态

        if (
            jump.includes(e.key) // 按下的是空格键
            && gravity
            && this._state.speed.y === 0
        ) {
            this._state.speed.setY(jumpPower);
        }


        keyDown && keyDown(e) // 键盘按下事件
    }

    keyup = (e: KeyboardEvent) => {

        const {
            forward = ["w"],
            backward = ["s"],
            left = ["a"],
            right = ["d"],
            keyUp
        } = this.props;

        forward.includes(e.key) && (this._state.forwarding = false); // 关闭前进状态
        backward.includes(e.key) && (this._state.backwarding = false); // 关闭后退状态
        left.includes(e.key) && (this._state.lefting = false); // 关闭左移状态
        right.includes(e.key) && (this._state.righting = false); // 关闭右移状态
        keyUp && keyUp(e);// 键盘弹起事件回调
    }

    // 鼠标移动
    mouseMove = (e: any) => {

        const {
            enabled = true,
            mouseSpeed = 1
        } = this.props;


        if (!enabled) {
            return;
        }

        if (this._state.move) {

            let movementX = e.movementX || e.mozMovementX || e.webkitMovementX || 0;
            let movementY = e.movementY || e.mozMovementY || e.webkitMovementY || 0;

            this.euler.setFromQuaternion(this.object3d.quaternion);
            this.euler.y -= movementX * mouseSpeed * 0.01; // 0.01 调慢一点
            this.euler.x -= movementY * mouseSpeed * 0.01; // 0.01 调慢一点

            this.euler.x = Math.max(Math.PI / 180 * -89.9, Math.min(Math.PI / 180 * 89.9, this.euler.x));

            this.object3d.quaternion.setFromEuler(this.euler);

            this.object3d.updateMatrix();
            // this.object3d.updateMatrixWorld();
        }
    }

    // 鼠标按下
    mousedown = (e: PointerEvent) => {
        const { renderer } = this.context;
        const { enabled = true, lock = false } = this.props;
        const { locked = false } = this._state;

        const canvas = renderer.domElement;

        if (enabled && lock && !locked) {
            canvas.requestPointerLock();
        } else {
            this._state.move = true;
        }
    }


    // 鼠标弹起
    mouseup = () => {
        // 没有在锁定状态时 鼠标弹起即可停止 移动
        const { lock } = this.props;
        if (!lock) {
            this._state.move = false;
        }
    }

    // 锁定鼠标事件
    pointerlockchange = (e: any) => {
        const { renderer } = this.context;
        const canvas = renderer.domElement;
        const { lockCall, unlockCall } = this.props;

        if (document.pointerLockElement === canvas) {
            lockCall && lockCall();
            this._state.move = true;
            this._state.locked = true;
        } else {
            unlockCall && unlockCall();
            this._state.locked = false;
            this._state.move = false;
        }
    }

    // 注册鼠标键盘事件
    registerEvent = () => {
        const { renderer } = this.context;
        const canvas = renderer.domElement;
        canvas.style.touchAction = 'none'; // disable touch scroll
        document.addEventListener("keydown", this.keydown);
        document.addEventListener("keyup", this.keyup);
        canvas.addEventListener("pointermove", this.mouseMove);
        canvas.addEventListener("pointerdown", this.mousedown);
        document.addEventListener("pointerup", this.mouseup);
        document.addEventListener("pointerlockchange", this.pointerlockchange);
    }

    // 取消鼠标键盘事件
    cancelEvent = () => {
        const { renderer } = this.context;
        const canvas = renderer.domElement;
        document.removeEventListener("keydown", this.keydown);
        document.removeEventListener("keyup", this.keyup);
        canvas.removeEventListener("pointermove", this.mouseMove);
        canvas.removeEventListener("pointerdown", this.mousedown);
        document.removeEventListener("pointerup", this.mouseup);
        document.removeEventListener("pointerlockchange", this.pointerlockchange);
        this._state.forwarding = false;
        this._state.backwarding = false;
        this._state.lefting = false;
        this._state.righting = false;
        this._state.move = false;
    }

    componentDidMount() {

        this.clock.getDelta();
        this.registerEvent();
        this.setControlsParame();

        this.computerPosition();

        const { renderer } = this.context;
        const canvas = renderer.domElement;

        canvas.addEventListener("mouseenter", this.registerEvent, false);

        // 失去焦点时 取消鼠标键盘事件
        canvas.addEventListener("mouseleave", this.cancelEvent, false);
    }

    componentDidUpdate() {
        this.setControlsParame();
    }

    componentWillUnmount() {
        this.cancelEvent();
    }

    // 不执行父类的此方法
    setTansformParam() { }

    // 不执行父类的此方法
    setObjectParam() { return false; }

    // 设置控制器的参数
    setControlsParame() {

        const {
            initTime = 0,
            bezier,//---贝塞尔曲线
            duration,//---------------------------过渡时间
            position
        } = this.props;


        if (this._state.initTime !== initTime) {

            this._state.initTime = initTime;

            this.Tposition
                .setBezier(bezier)
                .setDuration(duration)
                .reset({ ...this.object3d.position })
                .set({ ...xrt_util.parseVector3(position) })
        }

    }

    componentBeforeRender(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera) {

        const { gravity, moveSpeed = 0 } = this.props;

        const { forwarding, backwarding, lefting, righting } = this._state;

        const delta = Math.min(0.2, this.clock.getDelta()) * moveSpeed;
        this.positionIncremental.set(0, 0, 0);

        forwarding && (this.positionIncremental.z -= delta);
        backwarding && (this.positionIncremental.z += delta);
        lefting && (this.positionIncremental.x -= delta);
        righting && (this.positionIncremental.x += delta);

        this.positionIncremental.applyQuaternion(this.object3d.quaternion); // 计算移动水平世界向量

        this.positionIncremental.setLength(delta); // 预计需要移动的方向

        // 是否开启重力
        if (gravity) {
            this.positionIncremental.setY(0);
            FPSControls.vector3Temp.copy(this._state.addSpeed).multiplyScalar(delta * 2);
            this._state.speed.add(FPSControls.vector3Temp);
        }

        this.computerPosition();
    }

    // 计算控制器的位置
    computerPosition() {

        const { collideVolume = 3 } = this.props;

        // 位置移动过渡动画
        if (!this.Tposition.isDone()) {
            let { x, y, z } = this.Tposition.getJson();
            this.object3d.position.set(x, y, z);
        } else {

            this.positionIncremental.add(this._state.speed);

            const { collide } = this.props;

            if (collide) {

                let goOnComputer = true;
                let i = 0;

                // 最多循环100次
                while (goOnComputer && i < 100) {
                    goOnComputer = false;
                    i++;

                    [
                        this.rightToForwardRay,
                        this.leftToForwardRay,
                        this.leftToBackRay,
                        this.rightToBackRay,
                    ].map((ray, index) => {

                        this.object3d.getWorldPosition(FPSControls.vector3Temp);

                        let interset: THREE.Intersection<Object3D> | undefined;

                        let intersets = ray.getIntersetResult(this.positionIncremental);

                        if (intersets.length === 1) {

                            interset = intersets[0] as THREE.Intersection<Object3D>;

                        } else if (intersets.length > 1) {

                            interset = intersets[0] as THREE.Intersection<Object3D>;

                            intersets.map(int => {
                                if (interset && int.point.distanceTo(FPSControls.vector3Temp) < interset.point.distanceTo(FPSControls.vector3Temp)) {
                                    interset = int as THREE.Intersection<Object3D>;
                                }
                            });
                        }

                        if (interset) {
                            this.object3d.getWorldPosition(FPSControls.vector3Temp);
                            FPSControls.vector3Temp.sub(interset.point).setLength(0.03);
                            this.positionIncremental.add(FPSControls.vector3Temp);

                            goOnComputer = true;
                        }

                    });


                    [
                        this.upTodwomRay,
                        this.dwomToUpRay,
                    ].map((ray, index) => {

                        let interset: THREE.Intersection<Object3D> | undefined;

                        let intersets = ray.getIntersetResult(this.positionIncremental);

                        if (intersets.length === 1) {

                            interset = intersets[0] as THREE.Intersection<Object3D>;

                        } else if (intersets.length > 1) {

                            interset = (intersets[0] as any);

                            intersets.map(int => {
                                if (interset && int.point.distanceTo(FPSControls.vector3Temp) < interset.point.distanceTo(FPSControls.vector3Temp)) {
                                    interset = int as THREE.Intersection<Object3D>;
                                }
                            });
                        }

                        if (interset) {

                            FPSControls.vector3Temp.copy((interset.face as any)["worldNormal"]);

                            this._state.speed.set(0, 0, 0);

                            if (index === 0) {
                                FPSControls.vector3Temp.setX(0);
                                FPSControls.vector3Temp.setY(collideVolume - interset.distance);
                                FPSControls.vector3Temp.setZ(0);
                            } else {
                                FPSControls.vector3Temp.setLength(0.01);
                            }

                            this.positionIncremental.add(FPSControls.vector3Temp);
                            goOnComputer = true;

                        }
                    });

                }

            }

            //移动的方向
            this.object3d.position.add(this.positionIncremental);
        }

        return true;
    }

    render() {

        const { dev } = this.context;
        const { collideVolume = 3 } = this.props;

        return (
            <>
                {this.props.children}
                {
                    dev &&
                    <Line2
                        points={[[0, 0, collideVolume], [0, 0, -collideVolume]]}
                        color={["#fff", "#0f0"]}
                        linewidth={3}
                    />
                }

                <Group3D
                    rotation={(renderer, scene, camera, object3d) => {
                        return { x: -this.euler.x, y: 0, z: 0 }
                    }}
                >
                    {/* 上→下 */}
                    <Raycaster
                        ref={ray => ray && (this.upTodwomRay = ray)}
                        position={{ y: 0, x: 0, z: 0 }} // 射线的位置
                        direction={{ y: -collideVolume }}// 射线的方向
                    />

                    {/* 下→上 */}
                    <Raycaster
                        ref={ray => ray && (this.dwomToUpRay = ray)}
                        position={{ y: 0, x: 0, z: 0 }} // 射线的位置
                        direction={{ y: collideVolume }}// 射线的方向
                    />

                    {/* 右→前 */}
                    <Raycaster
                        ref={ray => ray && (this.rightToForwardRay = ray)}
                        position={{ x: collideVolume, y: 0, z: 0 }}
                        direction={{ x: -collideVolume, y: 0, z: -collideVolume }}
                    />

                    {/* 左→前 */}
                    <Raycaster
                        ref={ray => ray && (this.leftToForwardRay = ray)}
                        position={{ x: -collideVolume, y: 0, z: 0 }}
                        direction={{ x: collideVolume, y: 0, z: -collideVolume }}
                    />

                    {/* 左→后 */}
                    <Raycaster
                        ref={ray => ray && (this.leftToBackRay = ray)}
                        position={{ x: -collideVolume, y: 0, z: 0 }}
                        direction={{ x: collideVolume, y: 0, z: collideVolume }}
                    />

                    {/* 右→后 */}
                    <Raycaster
                        ref={ray => ray && (this.rightToBackRay = ray)}
                        position={{ x: collideVolume, y: 0, z: 0 }}
                        direction={{ x: -collideVolume, y: 0, z: collideVolume }}
                    />
                </Group3D>
            </>
        );
    }
}